package com.flying.acls.adapter.spring;


import com.flying.acls.domain.context.ContextStringValueResolver;
import com.flying.acls.domain.context.DoExprContextBuilder;
import com.flying.acls.domain.context.UrlExprContextBuilder;
import com.flying.acls.domain.permission.ClassPermissionBuilder;
import com.flying.acls.domain.resource.DoPatternBasedResourceBuilder;
import com.flying.acls.domain.resource.DoPatternInfo;
import com.flying.acls.domain.resource.NoOpUrlResourceBuilder;
import com.flying.acls.domain.service.AclService;
import com.flying.acls.domain.service.AclServiceImpl;
import com.flying.acls.domain.service.AntStyleMatcher;
import com.flying.acls.domain.service.Matcher;
import com.flying.acls.domain.sid.PatternBasedSidBuilder;
import com.flying.acls.domain.sid.SidPatternInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class AclBasedConfig {
    @Configuration
    @ConditionalOnExpression("${flying.security.acl.do.enabled:false} || ${flying.security.acl.url.enabled:false}")
    public static class CommonConfig {
        @Bean
        public ContextStringValueResolver stringValueResolver() {
            return new ContextStringValueResolver();
        }

        @Bean
        @ConditionalOnMissingBean(PatternBasedSidBuilder.class)
        public PatternBasedSidBuilder sidBuilder() {
            SidPatternInfo info = new SidPatternInfo(Collections.emptyList());
            return new PatternBasedSidBuilder(stringValueResolver(), info);
        }

        @Bean
        public ClassPermissionBuilder permissionBuilder() {
            return new ClassPermissionBuilder();
        }

        @Bean
        public Matcher antMatcher() {
            return new AntStyleMatcher(stringValueResolver());
        }

        @Bean
        public AclService aclService() {
            return new AclServiceImpl();
        }
    }

    @Configuration
    @ConditionalOnExpression("${flying.security.acl.do.enabled:false}")
    @Import(CommonConfig.class)
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public static class DoConfig {
        @Autowired
        private ContextStringValueResolver stringValueResolver;

        @Bean
        public DoExprContextBuilder doExpressionContextBuilder() {
            return new DoExprContextBuilder(stringValueResolver);
        }

        @Bean
        @ConditionalOnMissingBean(DoPatternBasedResourceBuilder.class)
        public DoPatternBasedResourceBuilder doResourceBuilder() {
            Map<String, DoPatternInfo> doPatternInfos = new HashMap<>();
            DoPatternInfo info = new DoPatternInfo("com.flying.acls.model.ResExpr", Collections.singletonList("${rcn}.${r.type}"));
            doPatternInfos.put(info.getClassName(), info);

            info = new DoPatternInfo("com.flying.acls.model.SidExpr", Collections.singletonList("${rcn}.${r.type}"));
            doPatternInfos.put(info.getClassName(), info);
            return new DoPatternBasedResourceBuilder(stringValueResolver, doPatternInfos);
        }

        @Bean
        public DoPermissionEvaluator aclPermissionEvaluator() {
            return new DoPermissionEvaluator();
        }

        @Bean
        public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
            DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
            expressionHandler.setPermissionEvaluator(aclPermissionEvaluator());
//        expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
            return expressionHandler;
        }
    }

    @Configuration
    @ConditionalOnProperty(name = "flying.security.acl.url.enabled", havingValue = "true", matchIfMissing = false)
    @Import(CommonConfig.class)
    public static class UrlConfig {
        @Autowired
        private ContextStringValueResolver stringValueResolver;

        @Bean
        public UrlExprContextBuilder urlExprContextBuilder() {
            return new UrlExprContextBuilder(stringValueResolver);
        }

        @Bean
        public NoOpUrlResourceBuilder noOpUrlResourceBuilder() {
            return new NoOpUrlResourceBuilder();
        }

        @Bean
        public UrlDecisionVoter urlDecisionVoter() {
            return new UrlDecisionVoter();
        }

        @Bean
        public AccessDecisionManager accessDecisionManager() {
            return new AffirmativeBased(Collections.singletonList(urlDecisionVoter()));
        }

        @Bean
        FilterInvocationSecurityMetadataSource aclFilterInvocationSecurityMetadataSource() {
            return new AclEntryFilterInvocationSecurityMetadataSource();
        }
    }
}